package com.example.basic;

import com.example.basic.snowflake.DefaultIdGenerator;
import com.example.basic.snowflake.IdGenerateException;
import com.example.basic.snowflake.IdGenerator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Set;
import java.util.concurrent.TimeUnit;

/**
 * 负载均衡, 多实例, 环形链表遍历
 */
public class LoadBalanceIdGenerator implements SnowflakeIdGenerator {
    private static final Logger log = LoggerFactory.getLogger(LoadBalanceIdGenerator.class);

    private final int maxChangeCount;
    private final CircleLinkedList linkedList = new CircleLinkedList();
    private Node current;

    public LoadBalanceIdGenerator(Set<Integer> workerIds) {
        this.maxChangeCount = workerIds.size() + 1;
        workerIds.forEach(this::addNode);
    }

    private void addNode(int workerId) {
        linkedList.addNode(new DefaultIdGenerator(OFFSET_CLOCK, workerId, BITS_ALLOCATOR));
        this.current = linkedList.head;
    }

    @Override
    public long generate() {
        int changeCount = 0;
        while (true) {
            try {
                return this.current.value.getUID();
            } catch (IdGenerateException e) {
                changeCount++;
                if (changeCount > maxChangeCount) {
                    throw new RuntimeException("已经切换一轮");
                }
                log.warn("UidGenerateException: {}, change workerId {} to {}", e.getMessage(), this.current.value.getWorkerId(), this.current.nextNode.value.getWorkerId());
                this.current = this.current.nextNode;
                sleep();
            }
        }
    }

    private static void sleep() {
        try {
            TimeUnit.MILLISECONDS.sleep(20);
        } catch (InterruptedException ignored) {
        }
    }


    private static class CircleLinkedList {
        // 环形链表
        private Node head = null;
        private Node tail = null;
        private Node current;
        private void addNode(IdGenerator generator) {
            Node newNode = new Node(generator);
            if (head == null) {
                head = newNode;
            } else {
                tail.nextNode = newNode;
            }
            tail = newNode;
            tail.nextNode = head;
            this.current = head;
        }
    }

    private static class Node {
        final IdGenerator value;
        Node nextNode;
        public Node(IdGenerator value) {
            this.value = value;
        }
    }

}
